#include "StdAfx.h"
#include "Chunk.h"
#include "plane.h"
#include "ChunkItem.h"
#include "ChunkBoundary.h"
#include "ChunkSpace.h"
#include "Hull.h"
#include "ChunkFileSerializer.h"
#include "ChunkManager.h"


using namespace ChunkSystem;
using namespace Common;

float	Chunk::m_fMaxHeight = 100.0f;
float	Chunk::m_fMinHeight = -100.0f;

Chunk::Chunk( const String& strChunkId, ChunkItem* pOwner, ChunkSpace* pOwnerSpace )
	:ChunkItem(strChunkId,pOwner),m_pOwnerSpace(pOwnerSpace),m_curState(eUnload)
{
	assert(m_pOwnerSpace != NULL);

	IRenderer* pRenderer = ChunkManager::GetInstance().GetRenderer();
	if( pRenderer != NULL )
	{
		IRenderableFactory* pFactory = pRenderer->GetRenderableFactory();
		m_pVisible = pFactory->Create( this );
	}
}

Chunk::~Chunk(void)
{
	this->Unload();
}

bool Chunk::_CanBind( Portal& portalA, Portal& portalB, Chunk* chunkA, Chunk* chunkB )
{
	/*
	assert( chunkA != chunkB );

	// ensure both the portals are available (ie, not heaven, earth, or invasive)
	if ((portalA.pChunk != NULL && !portalA.hasChunk()) ||
		(portalB.pChunk != NULL && !portalB.hasChunk()) )
	{
		return false;
	}

	if (portalA.points.size() != portalB.points.size())
	{
		return false;
	}

	if (! almostEqual( ( portalA.centre - portalB.centre ).lengthSquared(), 0.f ))
	{
		return false;
	}

	Vector3 n1 = chunkA->transform().applyVector(portalA.plane.normal());
	Vector3 n2 = chunkB->transform().applyVector(portalB.plane.normal());

	// Check normals are opposite
	if (! almostEqual( ( n1 + n2 ).length(), 0.f ))
	{
		return false;
	}

	std::vector< Vector3 > points;

	for (unsigned int i = 0; i < portalA.points.size(); ++i)
	{
		Vector3 v = chunkA->transform().applyPoint( portalA.objectSpacePoint( i ) );
		points.push_back( v );
	}

	for (unsigned int i = 0; i < portalA.points.size(); ++i)
	{
		Vector3 v = chunkB->transform().applyPoint( portalB.objectSpacePoint( i ) );
		std::vector< Vector3 >::iterator iter = points.begin();

		for (; iter != points.end(); ++iter)
		{
			if (almostEqual( v, *iter ))
			{
				break;
			}
		}

		if (iter == points.end())
		{
			return false;
		}
	}
	*/
	return true;
}

/*
bool Chunk::_AutomaticBind( Chunk* pTargetChunk, Portal& oPortal )
{
	// first see if we already have a portal that fits the bill

	// go through all our boundaries
	// we won't snap non-invasive shell portal to an outdoor chunk
	if (oPortal.isInvasive() || ( !oPortal.isInvasive() && !this->isOutsideChunk()))
	{
		for ( Boundaries::iterator itrBoundary = m_Joints.begin();itrBoundary != m_Joints.end(); itrBoundary++)
		{
			// go through all their unbound portals
			ChunkBoundary::Portals& UnboundPortals = (*itrBoundary)->GetUnboundPortals();
			for (ChunkBoundary::Portals::iterator itrUnboundPortal = UnboundPortals.begin(); itrUnboundPortal != UnboundPortals.end(); itrUnboundPortal++)
			{
				if ( _CanBind( oPortal, **itrUnboundPortal, pTargetChunk, this ) )
				{
					(*itrUnboundPortal)->BindChunk(pTargetChunk);

					// ok that's it. we leave it unbound for now as
					// it will soon be bound by an ordinary 'bind' call.
					return true;
				}

				// we could recalculate centres, but we may as well use
				//  the existing cached ones
			}
		}
	}

	// ok we didn't find anything to connect to.
	// if the other chunk's portal isn't invasive, or if
	//  we don't want to be invaded, then no connection is made.
	if (!oportal.isInvasive() || !this->isOutsideChunk()) return false;

	// we'd better form that portal then
	const Plane& rPlane = oPortal.GetPlane();
	const Vector3 & fnormal = rPlane.
	Vector3 wnormal = pChunk->transform_.applyVector( fnormal ) * -1.f;
	Vector3 wcentre = oportal.centre;				// facing other way
//	PlaneEq wplane( wcentre, wnormal );
	Vector3 lnormal = transformInverse_.applyVector( wnormal );
	Vector3 lcentre = transformInverse_.applyPoint( wcentre );
	PlaneEq lplane( lcentre, lnormal );

	// see if any existing planes fit
	bool isInternal = false;
	ChunkBoundaries::iterator bit;
	
	bit = joints_.end();

	// ok, make a new one then
	if (bit == joints_.end())
	{
		isInternal = true;

		ChunkBoundary * pCB = new ChunkBoundary( NULL, pMapping_ );
		pCB->plane_ = lplane;
		joints_.push_back( pCB );
		bit = joints_.end() - 1;
	}

	// make up the portal on it
	ChunkBoundary::Portal * portal =
		new ChunkBoundary::Portal( NULL, (*bit)->plane_, pMapping_ );
	portal->internal = isInternal;
	portal->pChunk = pChunk;

	// Figure out the basis for the polygon in this chunk's local space

	// 1) Find the cartesian axis that is most perpendicular to the lnormal
	// vector.
	// 1.a) Take the dot product of the lnormal vector with each axis
	float NdotX = lnormal.dotProduct( Vector3(1.0f, 0.0f, 0.0f) );
	float NdotY = lnormal.dotProduct( Vector3(0.0f, 1.0f, 0.0f) );
	float NdotZ = lnormal.dotProduct( Vector3(0.0f, 0.0f, 1.0f) );

	// 1.b) The value which is closest to zero represents the cartesian
	// axis that is the most perpendicular to the lnormal vector
	Vector3 cartesianAxis;

	// First test X against Y
	if ( fabsf(NdotX) < fabsf(NdotY) )
		// If here, test X against Z
		if ( fabsf(NdotX) < fabsf(NdotZ) )
			// X most perpendicular
			cartesianAxis = Vector3(1.0f, 0.0f, 0.0f);
		else
			// Z most perpendicular
			cartesianAxis = Vector3(0.0f, 0.0f, 1.0f);
	else
		// If here, test Y against Z
		if ( fabsf(NdotY) < fabsf(NdotZ) )
			// Y most perpendicular
			cartesianAxis = Vector3(0.0f, 1.0f, 0.0f);
		else
			// Z most perpendicular
			cartesianAxis = Vector3(0.0f, 0.0f, 1.0f);

	// 2) Now that the most perpendicular axis has been found, it can
	// be used to find the tangent vector, luAxis
	Vector3 luAxis = lnormal.crossProduct( cartesianAxis );

	// 3) The normal and the tangent vectors can now be used to find the
	// binormal (remember cartesianAxis was only the closest perpendicular
	// axis, it probably isn't going to be perpendicular)
	Vector3 lvAxis = lnormal.crossProduct( luAxis );

	// turn it into a matrix (actually using matrix for ordinary maths!)
	Matrix basis = Matrix::identity;
	memcpy( basis[0], luAxis, sizeof(Vector3) );
	memcpy( basis[1], lvAxis, sizeof(Vector3) );
	memcpy( basis[2], lnormal, sizeof(Vector3) );
		// error from plane is in the z.
	basis.translation( lnormal * lplane.d() / lnormal.lengthSquared() );
	Matrix invBasis; invBasis.invert( basis );

	// use it to convert the world coordinates of the points into local space
	for (uint i = 0; i < oportal.points.size(); i++)
	{
		// point starts in form portal's space
		Vector3 fpt =
			oportal.uAxis * oportal.points[i][0] +
			oportal.vAxis * oportal.points[i][1] +
			oportal.origin;
		// now in form chunk's space
		Vector3 wpt = pChunk->transform_.applyPoint( fpt );
		// now in world space
		Vector3 lpt = transformInverse_.applyPoint( wpt );
		// now in our chunk's space
		Vector3 ppt = invBasis.applyPoint( lpt );
		// and finally in our portal's space
		portal->points.push_back( Vector2( ppt.x, ppt.y ) );
	}
	portal->uAxis = basis.applyToUnitAxisVector(0); //luAxis;
	portal->vAxis = basis.applyToUnitAxisVector(1); //lvAxis;
	portal->origin = basis.applyToOrigin();
	portal->lcentre = transformInverse_.applyPoint( wcentre );
	portal->centre = wcentre;

	// now do the dodgy reverse portal hack thing, from ChunkBoundary::Portal
	// hack for 4-sided polygons - reverse order of two middle points
	// if plane from first three points points the wrong way.
	if (portal->points.size() == 4)
	{
		PlaneEq testPlane(
			portal->points[0][0] * portal->uAxis + portal->points[0][1] * portal->vAxis + portal->origin,
			portal->points[1][0] * portal->uAxis + portal->points[1][1] * portal->vAxis + portal->origin,
			portal->points[2][0] * portal->uAxis + portal->points[2][1] * portal->vAxis + portal->origin );
		Vector3 n1 = (*bit)->plane_.normal();
		Vector3 n2 = testPlane.normal();
		n1.normalise();	n2.normalise();
		if ((n1 + n2).length() < 1.f)	// should be 2 if equal
		{
			Vector2 tpt = portal->points[1];
			portal->points[1] = portal->points[3];
			portal->points[3] = tpt;
		}
	}

	// and add it as an unbound portal
	(*bit)->addInvasivePortal( portal );

	// let the caches know things have changed
	this->notifyCachesOfBind( false );

	// and record if we now have internal chunks
	hasInternalChunks_ |= isInternal;

	return true;
}
*/

void Chunk::Unload( void )
{
	if( m_curState == eLoading )
		assert( false );

	if( m_curState != eLoaded && m_curState != eBinded )
		return;

	std::for_each( m_includeChildren.begin(), m_includeChildren.end(), std::mem_fun(&ChunkItem::Unload) );
	m_includeChildren.clear();

	m_curState = eUnload;

	m_pVisible->Unload();
}

void Chunk::Loose( void )
{
	//TODO:
	m_curState = eLoaded;
}

bool Chunk::Load( TiXmlElement* pDataRoot )
{
	bool bResult = true;
	//step1:form boundaries
	if( !this->GenerateBoudaries(pDataRoot) )
	{
		assert(false);
	}
	
	//step2:load internal contents
	ChunkFileSerializer* pChunkFileSerializer = ChunkManager::GetInstance().GetChunkSerialzer();
	for( uint16 iChunkItemType = ChunkFileSerializer::eModel ; iChunkItemType != ChunkFileSerializer::eEnd ; ++iChunkItemType )
		pChunkFileSerializer->CreateChunkItemFromFile( this, pDataRoot, (ChunkFileSerializer::ChunkItemType)iChunkItemType );

	//step3:transform the chunk and update AABB
	//create visible for testing.
	IRenderer* pRenderer = ChunkManager::GetInstance().GetRenderer();
	assert( pRenderer != NULL );
	m_pVisible->Load( pRenderer );

	/*
	int iObjectCount = static_cast<int>( Math::RangeRandom( 1.0f, 100.0f ) );
	for( int iObjIdx = 0; iObjIdx <= iObjectCount; ++iObjIdx )
	{
		float fConstructTime = Math::RangeRandom( 1.0f, 10.0f );
		::Sleep( fConstructTime );

		this->_OnLoad( (float)iObjIdx / (float)iObjectCount * 100 );
	}
	*/

	//step4:marked as loaded

	return bResult;
}

void Chunk::Focus()
{
	// figure out the border
	HullBorder	border;
	
	Boundaries::iterator itrBoundary = m_Boundaries.begin();
	for( ; itrBoundary != m_Boundaries.end() ; ++itrBoundary )
	{
		const Plane& pBound = (*itrBoundary)->GetPlane();
		//transform the plane from object space to world space
		Vector3 ndtr = m_matWorldTM * ( pBound.normal * pBound.d );
		Vector3 ntr  = m_matWorldTM * ( pBound.normal );

		border.push_back( Plane( ntr, ntr.dotProduct( ndtr ) ) );
	}

	// find what columns we need to add to
	ColumnSet columns;
	if ( *m_strId.rbegin() == 'o')
	{
		// the following will create the column in pSpace if it is needed.
		columns.insert( m_pOwnerSpace->GetColumn( m_matWorldTM.getTrans(), true ) );
	}
	else
	{
		const Vector3 & mb = m_aabb.getMinimum();
		const Vector3 & Mb = m_aabb.getMaximum();
		for (int i = 0; i < 8; i++)
		{
			Vector3 pt(	(i&1) ? Mb.x : mb.x,
						(i&2) ? Mb.y : mb.y,
						(i&4) ? Mb.z : mb.z );

			columns.insert( m_pOwnerSpace->GetColumn( pt , true ) );
		}
	}

	// and add it to all of them
	for (ColumnSet::iterator it = columns.begin(); it != columns.end(); it++)
		(*it)->AddChunk( border, this );
}

bool Chunk::GenerateBoudaries( TiXmlElement* pDataSection )
{
	BoundariesData	conBoundariesData;
	bool bResult = true;
	
	bResult = _LoadBoundaryInfo( pDataSection, conBoundariesData );

	ItrBoundariesData pos = conBoundariesData.begin();
	for(;pos != conBoundariesData.end() ; ++pos )
	{
		std::auto_ptr<ChunkBoundary> ptrChunkBoundary( new ChunkBoundary(this) );
		if( !ptrChunkBoundary->Load(*pos) )
		{
			bResult = false;
			continue;
		}

		const Plane& rPlane = ptrChunkBoundary->GetPlane();
		if ( rPlane.normal.length() == 0.f)
		{
			bResult = false;
			continue;
		}

		bool isaBound = false;
		bool isaJoint = false;

		ChunkBoundary::Portals unboundPortals = ptrChunkBoundary->GetUnboundPortals();
		ChunkBoundary::Portals boundPortals = ptrChunkBoundary->GetBoundPortals();

		if ( unboundPortals.size() )
		{
			isaJoint = true;
			if ( unboundPortals[0]->GetPortalType() != Portal::eInvasive )
			{
				// we only need to check the first portal
				// because if there are any non-internal
				// portals then the ChunkBoundary must
				// be a bound, (because chunks are convex),
				// and the portal should be internal.
				isaBound = true;
			}
		}
		else
		{
			// the only portals bound at this time are those
			// connecting to heaven or earth.
			if (boundPortals.size())
			{
				isaJoint = true;
			}
			isaBound = true;
		}

		if (isaBound) m_Boundaries.push_back( ptrChunkBoundary.get() );
		if (isaJoint) m_Joints.push_back( ptrChunkBoundary.get() );

		ptrChunkBoundary.release();
	}

	return bResult && conBoundariesData.size() >= 4;
}

void Chunk::AddChunkItem( ChunkItem* pChunkItem )
{
	ItrIncludeChildren pos = std::find( m_includeChildren.begin() , m_includeChildren.end() , pChunkItem );
	if( pos != m_includeChildren.end() )
		return;

	m_includeChildren.push_back(pChunkItem);
}

Chunk::IncludeChildren& Chunk::GetIncludeChildren( void )
{
	return m_includeChildren;
}

void Chunk::Bind( void )
{
	//step0: sync init chunk item
	IncludeChildren::iterator itrChild = m_includeChildren.begin();
	for( ; itrChild != m_includeChildren.end() ; ++itrChild )
	{
		if( !(*itrChild)->InitSync() )
			assert(false);
	}
	//step1: bind all portals
	this->BindPorts();
	//step2: marked binded
	m_curState = eBinded;

	m_pOwnerSpace->NoticeChunk( this );
}

void Chunk::Bind( Chunk* pChunk )
{
	// go through all our boundaries
	for ( Boundaries::iterator itrJoint = m_Joints.begin();
		itrJoint != m_Joints.end();
		itrJoint++)
	{
		ChunkBoundary::Portals UnboundPortals = (*itrJoint)->GetUnboundPortals();
		// go through all their unbound portals
		for (ChunkBoundary::Portals::iterator itrPortal = UnboundPortals.begin();
			itrPortal != UnboundPortals.end(); itrPortal++)
		{
			// see if this is the one
			if ( (*itrPortal)->GetBindChunk() == pChunk)
			{
				(*itrJoint)->BindPortal( itrPortal - UnboundPortals.begin() );

				//this->notifyCachesOfBind( false );

				// we return here - if there is more than one
				// portal from that chunk then we'll get another
				// bind call when it finds the other one :)
				return;
			}
		}
	}
}

void Chunk::BindPorts( void )
{
	// go through all our boundaries
	for (Boundaries::iterator itrBoundary = m_Joints.begin(); itrBoundary != m_Joints.end(); itrBoundary++)
	{
		// go through all their unbound portals
		ChunkBoundary::Portals& UnboundPortals = (*itrBoundary)->GetUnboundPortals();
		for (size_t idx = 0; idx < UnboundPortals.size(); idx++)
		{
			// get the portal 
			Portal& rPortal = *UnboundPortals.at(idx);

			Chunk* pBindChunk = rPortal.GetBindChunk();
			if( pBindChunk == NULL )
			{
				//if (p.pChunk != NULL && !p.isInvasive()) continue;

				// ok, we want to give it one then
				// look at point 10cm away from centre of portal
				Vector3 conPt = m_matWorldTM * ( (*itrBoundary)->GetPlane().normal * -0.001f + rPortal.GetLocalCenter() );

				Chunk* pFound = NULL;
				Column* pCol = m_pOwnerSpace->GetColumn( conPt );
				if ( pCol != NULL )
					pFound = pCol->FindChunk( conPt, this );

				if ( pFound == NULL )
					continue;

				// see if it wants to form a boundary with us
				if ( !pFound->_FormPortal( this, rPortal ))
					continue;

				// this is the chunk for us then
				rPortal.BindChunk( pFound );

				// split it if it extends beyond just this chunk
				//(*bit)->splitInvasivePortal( this, i );
				// (the function above may modify unboundPortals_, but that
				// OK as it is a vector of pointers; 'p' is not clobbered)
				// if portals were appended we'll get to them in a later cycle
			}

			pBindChunk->GetOwnerSpace()->AddIdenticalChunk( pBindChunk );
			rPortal.BindChunk( pBindChunk );

			// create a chunk exit portal item, mainly for rain but who knows
			// what else this will be used for..
			/*
			if (!this->isOutsideChunk_ && p.pChunk->isOutsideChunk())
			{
				this->addStaticItem( new ChunkExitPortal(p) );
			}
			*/

			// if it's already bound, then get it to bind to this portal too
			if ( pBindChunk->m_curState == eBinded )
			{
				// save chunk pointer before invalidating reference...
				Chunk* pBindChunk = rPortal.GetBindChunk();

				// move it to the bound portals list
				(*itrBoundary)->BindPortal( idx-- );

				// and let it know we're online
				pBindChunk->Bind( this );
			}
		}
	}
}

void Chunk::DrawVisible( IRenderer* pRenderer )
{
	if( pRenderer == NULL || m_pVisible == NULL )
		return;

	m_pVisible->Draw( pRenderer );

	std::for_each( m_includeChildren.begin(), m_includeChildren.end(), std::bind2nd( std::mem_fun1(&ChunkItem::DrawVisible), pRenderer ) );
}

void Chunk::CreateVisible(IRenderableFactory* pFac)
{
	if( m_pVisible == NULL )
		m_pVisible = pFac->Create(this);

	std::for_each( m_includeChildren.begin(), m_includeChildren.end(), std::bind2nd( std::mem_fun1(&ChunkItem::CreateVisible), pFac ) );
}

void Chunk::DestoryVisible(void)
{
	if( m_pVisible != NULL )
	{
		delete m_pVisible;
		m_pVisible = NULL;
	}

	std::for_each( m_includeChildren.begin(), m_includeChildren.end(), std::mem_fun(&ChunkItem::DestoryVisible) );
}

bool Chunk::_FormPortal( Chunk* pToChunk, Portal& oportal )
{
	return true;
}

const Chunk::Boundaries& Chunk::GetBoundaries( void ) const
{
	return m_Joints;
}

HullTree::HullOutLines& Chunk::GetHullOutLines( void )
{
	return m_HullOutLines;
}
